//
//  CLCoreText.m
//  CLBrowser
//
//  Created by 梁慧聪 on 2022/7/16.
//
#import "CLCoreText.h"
#import <Foundation/Foundation.h>
#import <CoreText/CoreText.h>
#import <CoreGraphics/CoreGraphics.h>
@implementation CLCoreText
/// 获得 CTFrame
/// - Parameters:
///   - attrString: 内容
///   - rect: 显示范围
/// - Returns: CTFrame
+ (CTFrameRef)getFrameRef:(NSAttributedString *)attrString rect:(CGRect)rect {
    CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFTypeRef)attrString);
    CGPathRef path = CGPathCreateWithRect(rect, nil);
    return CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, nil);
}
/// 获得内容分页列表
/// - Parameters:
///   - attrString: 内容
///   - rect: 显示范围
/// - Returns: 内容分页列表
+ (NSArray <NSValue *>*)getPageingRanges:(NSAttributedString *)attrString rect:(CGRect)rect {
    NSMutableArray <NSValue *>* rangeArray = [NSMutableArray.alloc init];
    CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFTypeRef)attrString);
    CGPathRef path = CGPathCreateWithRect(rect, NULL);
    CFRange range = CFRangeMake(0, 0);
    NSInteger rangeOffset = 0;
    do {
        CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(rangeOffset, 0), path, nil);
        range = CTFrameGetVisibleStringRange(frame);
        [rangeArray addObject:[NSValue valueWithRange:NSMakeRange(rangeOffset, range.length)]];
        rangeOffset += range.length;
    } while(range.location + range.length < attrString.length);
    return rangeArray;
}
/// 获得触摸位置文字的Location
/// - Parameters:
///   - point: 触摸位置
///   - frameRef: 内容 CTFrame
/// - Returns: 触摸位置的Index
+ (CFIndex)getTouchLocation:(CGPoint)point frameRef:(CTFrameRef)frameRef {
    CFIndex location = -1;
    CTLineRef line = [self getTouchLine:point frameRef:frameRef];
    if (line != NULL) {
        location = CTLineGetStringIndexForPosition(line, point);
    }
    return location;
}
/// 获得触摸位置那一行文字的Range
/// - Parameters:
///   - point: 触摸位置
///   - frameRef: 内容 CTFrame
/// - Returns: 一行的 NSRange
+ (NSRange)getTouchLineRange:(CGPoint)point frameRef:(CTFrameRef)frameRef {
    CTLineRef line = [self getTouchLine:point frameRef:frameRef];
    return [self getLineRange:line];
}
/// 获得触摸位置那一个段落的 NSRange || 一行文字的 NSRange
/// - Parameters:
///   - point: 触摸位置
///   - frameRef: 内容 CTFrame
///   - content: 内容字符串，传了则获取长按的段落 NSRange，没传则获取一行文字的 NSRange
/// - Returns: 一个段落的 NSRange || 一行文字的 NSRange
+ (NSRange)getTouchParagraphRange:(CGPoint)point frameRef:(CTFrameRef)frameRef content:(NSString *)content {
    CTLineRef line = [self getTouchLine:point frameRef: frameRef];
    NSRange range =  [self getLineRange:line];
    // 如果有正文，则获取整个段落的 NSRange
    if (line != nil && content != nil) {
        CFArrayRef lines = CTFrameGetLines(frameRef);
        CFIndex count = CFArrayGetCount(lines);
        CFRange ctrange = {0, count};
        CFIndex index = CFArrayGetFirstIndexOfValue(lines, ctrange, line);
        CFIndex num = 0;
        NSRange rangeHeader = range;
        NSRange rangeFooter = range;
        BOOL isHeader = NO;
        BOOL isFooter = NO;
        do {
            if (!isHeader) {
                CFIndex newIndex = index - num;
                CTLineRef line = CFArrayGetValueAtIndex(lines, newIndex);
                rangeHeader = [self getLineRange:line];
                NSString *headerString = [content cl_subStringFromRange:rangeHeader];
                isHeader = [headerString containsString:CL_READ_PH_SPACE];
                if (newIndex == 0) { isHeader = YES; }
            }
            if (!isFooter) {
                CFIndex newIndex = index + num;
                CTLineRef line = CFArrayGetValueAtIndex(lines, newIndex);
                rangeFooter =  [self getLineRange:line];
                NSString *footerString = [content cl_subStringFromRange:rangeFooter];
                isFooter = [footerString containsString:@"\n"];
                if (newIndex == (count - 1)) { isFooter = YES; }
            }
            num += 1;
        } while (!isHeader || !isFooter);
        range = NSMakeRange(rangeHeader.location, rangeFooter.location + rangeFooter.length - rangeHeader.location);
    }
    return range;
}
/// 获得触摸位置在哪一行
/// - Parameters:
///   - point: 触摸位置
///   - frameRef: 内容 CTFrame
/// - Returns: CTLine
+ (CTLineRef)getTouchLine:(CGPoint)point frameRef:(CTFrameRef)frameRef {
    CTLineRef line = nil;
    if (frameRef == nil) { return line; }
    CGPathRef path = CTFrameGetPath(frameRef);
    CGRect bounds = CGPathGetPathBoundingBox(path);
    CFArrayRef lines = CTFrameGetLines(frameRef);
    NSUInteger lineCount = CFArrayGetCount(lines);
    if (lineCount == 0) { return line; }
    CGPoint *origins = malloc(lineCount * sizeof(CGPoint));
    if (origins == NULL) { return line; }
    CTFrameGetLineOrigins(frameRef, CFRangeMake(0, lineCount), origins);
    for (int i = 0; 0 < lineCount; i++) {
        CTLineRef tempLine = CFArrayGetValueAtIndex(lines, i);
        CGPoint origin = origins[i];
        CGFloat lineAscent = 0;
        CGFloat lineDescent = 0;
        CGFloat lineLeading = 0;
        CTLineGetTypographicBounds(tempLine, &lineAscent, &lineDescent, &lineLeading);
        CGFloat lineWidth = bounds.size.width;
        CGFloat lineheight = lineAscent + lineDescent + lineLeading;
        CGRect lineFrame = CGRectMake(origin.x, bounds.size.height - origin.y - lineAscent, lineWidth, lineheight);
        lineFrame = CGRectInset(lineFrame, -CL_SPACE_5, -CL_SPACE_5);
        if (CGRectContainsPoint(lineFrame, point)) {
            line = tempLine;
            break;
        }
    }
    free(origins);
    return line;
}
/// 获取内容所有段落尾部 NSRange
/// - Parameters:
///   - frameRef: 内容 CTFrame
///   - content: 内容字符串，也就是生成 frameRef 的正文内容
/// - Returns: [NSRange] 传入内容的所有断尾 NSRange
+ (NSArray <NSValue *>*)getParagraphEndRanges:(CTFrameRef)frameRef content:(NSString *)content {
    NSMutableArray <NSValue *>*ranges = [NSMutableArray.alloc init];
    if (frameRef != nil && content != nil) {
        CFArrayRef lines = CTFrameGetLines(frameRef);
        NSUInteger lineCount = CFArrayGetCount(lines);
        if (lineCount == 0) { return ranges; }
        CGPoint *origins = malloc(lineCount * sizeof(CGPoint));
        if (origins == NULL) { return ranges; }
        CTFrameGetLineOrigins(frameRef, CFRangeMake(0, lineCount), origins);
        for (int i = 0; 0 < lineCount; i++) {
            CTLineRef line = CFArrayGetValueAtIndex(lines, i);
            NSRange lintRange = [self getLineRange:line];
            NSString *lineString = [content cl_subStringFromRange:lintRange];//content?.substring(lintRange)
            BOOL isEnd = [lineString containsString:@"\n"];
            if (isEnd) { [ranges addObject:[NSValue valueWithRange:lintRange]]; }
        }
    }
    return ranges;
}
/// 获取内容所有段落尾部 CGRect
///
/// - Parameters:
///   - frameRef: 内容 CTFrame
///   - content: 内容字符串，也就是生成 frameRef 的正文内容
/// - Returns: [CGRect] 传入内容的所有断尾 CGRect
+ (NSArray <NSValue *>*)GetParagraphEndRects:(CTFrameRef)frameRef content:(NSString *)content {
    NSMutableArray <NSValue *>*rects = [NSMutableArray.alloc init];
    NSArray<NSValue *>* ranges = [self getParagraphEndRanges:frameRef content:content];
    for (NSValue *rangeVal in ranges) {
        NSRange range = [rangeVal rangeValue];
        NSArray <NSValue *>* rect = [self getRangeRects:range frameRef:frameRef content:content];
        [rects addObjectsFromArray:rect];
    }
    return rects;
}
/// 通过 range 返回字符串所覆盖的位置 [CGRect]
/// - Parameter range: NSRange
/// - Parameter frameRef: 内容 CTFrame
/// - Parameter content: 内容字符串(有值则可以去除选中每一行区域内的 开头空格 - 尾部换行符 - 所占用的区域,不传默认返回每一行实际占用区域)
/// - Returns: 覆盖位置[CGRect]
+ (NSArray <NSValue *>*)getRangeRects:(NSRange)range frameRef:(CTFrameRef)frameRef content:(NSString *)content {
    NSMutableArray <NSValue *>*rects = [[NSMutableArray alloc]init];
    if (frameRef == NULL ){ return rects; }
    if (range.length == 0 || range.location == NSNotFound) { return rects; }
    CFArrayRef lines = CTFrameGetLines(frameRef);
    NSUInteger lineCount = CFArrayGetCount(lines);
    if (lineCount == 0) { return rects; }
    CGPoint *origins = malloc(lineCount * sizeof(CGPoint));
    if (origins == NULL) { return rects; }
    CTFrameGetLineOrigins(frameRef, CFRangeMake(0, 0), origins);
    for (int i = 0; i < lineCount; i++) {
        CTLineRef line = CFArrayGetValueAtIndex(lines, i);
        CFRange lineCFRange = CTLineGetStringRange(line);
        NSRange lineRange = NSMakeRange(lineCFRange.location == kCFNotFound ? NSNotFound : lineCFRange.location, lineCFRange.length);
        NSRange contentRange = NSMakeRange(NSNotFound, 0);
        if ((lineRange.location + lineRange.length) > range.location && lineRange.location < (range.location + range.length)) {
            contentRange.location = MAX(lineRange.location, range.location);
            NSUInteger end = MIN(lineRange.location + lineRange.length, range.location + range.length);
            contentRange.length = end - contentRange.location;
        }
        if (contentRange.length > 0) {
            // 去掉 -> 开头空格 - 尾部换行符 - 所占用的区域
            if (!NULLString(content)) {
                NSString *tempContent = [content cl_subStringFromRange:contentRange];
                NSArray<NSTextCheckingResult *>* spaceRanges = [tempContent matchesByPattern:@"\\s\\s"];
                if (spaceRanges.count) {
                    NSRange spaceRange = spaceRanges.firstObject.range;
                    contentRange = NSMakeRange(contentRange.location + spaceRange.length, contentRange.length - spaceRange.length);
                }
                NSArray<NSTextCheckingResult *>* enterRanges = [tempContent matchesByPattern:@"\\n"];
                if (enterRanges.count) {
                    NSRange enterRange = enterRanges.firstObject.range;
                    contentRange = NSMakeRange(contentRange.location, contentRange.length - enterRange.length);
                }
            }
            // 正常使用(如果不需要排除段头空格跟段尾换行符可将上面代码删除)
            CGFloat xStart = CTLineGetOffsetForStringIndex(line, contentRange.location, nil);
            CGFloat xEnd = CTLineGetOffsetForStringIndex(line, contentRange.location + contentRange.length, nil);
            CGPoint origin = origins[i];
            CGFloat lineAscent = 0;
            CGFloat lineDescent = 0;
            CGFloat lineLeading = 0;
            CTLineGetTypographicBounds(line, &lineAscent, &lineDescent, &lineLeading);
            CGRect contentRect = CGRectMake(origin.x + xStart, origin.y - lineDescent, fabs(xEnd - xStart), lineAscent + lineDescent + lineLeading);
            [rects addObject:[NSValue valueWithCGRect:contentRect]];
        }
    }
    free(origins);
    return rects;
}
/// 通过 range 获得合适的 MenuRect
/// - Parameter rects: [CGRect]
/// - Parameter frameRef: 内容 CTFrame
/// - Parameter viewFrame: 目标ViewFrame
/// - Parameter content: 内容字符串
/// - Returns: MenuRect
+ (CGRect)getMenuRect:(NSRange)range frameRef:(CTFrameRef)frameRef viewFrame:(CGRect)viewFrame content:(NSString *)content {
    NSArray<NSValue *> *rects = [self getRangeRects:range frameRef:frameRef content:content];
    return [self getMenuRect:rects viewFrame:viewFrame];
}
/// 通过 [CGRect] 获得合适的 MenuRect
/// - Parameter rects: [CGRect]
/// - Parameter viewFrame: 目标ViewFrame
/// - Returns: MenuRect
+ (CGRect)getMenuRect:(NSArray<NSValue *>*)rects viewFrame:(CGRect)viewFrame {
    CGRect menuRect = CGRectZero;
    if (rects.count == 0) { return menuRect; }
    if (rects.count == 1) {
        menuRect = [rects.firstObject CGRectValue];
    } else {
        menuRect = [rects.firstObject CGRectValue];
        NSUInteger count = rects.count;
        for (int i = 0;i < count;i++)  {
            CGRect rect = [rects[i] CGRectValue];
            CGFloat minX = MIN(menuRect.origin.x, rect.origin.x);
            CGFloat maxX = MAX(menuRect.origin.x + menuRect.size.width, rect.origin.x + rect.size.width);
            CGFloat minY = MIN(menuRect.origin.y, rect.origin.y);
            CGFloat maxY = MAX(menuRect.origin.y + menuRect.size.height, rect.origin.y + rect.size.height);
            menuRect.origin.x = minX;
            menuRect.origin.y = minY;
            menuRect.size.width = maxX - minX;
            menuRect.size.height = maxY - minY;
        }
    }
    menuRect.origin.y = viewFrame.size.height - menuRect.origin.y - menuRect.size.height;
    return menuRect;
}
/// 获取指定内容高度
/// - Parameters:
///   - attrString: 内容
///   - maxW: 最大宽度
/// - Returns: 当前高度
+ (CGFloat)getAttrStringHeight:(NSAttributedString *)attrString maxW:(CGFloat)maxW {
    CGFloat height = 0;
    if (attrString.length > 0) {
        // 注意设置的高度必须大于文本高度
        CGFloat maxH = 1000;
        CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFTypeRef)attrString);
        CGRect drawingRect = CGRectMake(0, 0, maxW, maxH);
        CGPathRef path = CGPathCreateWithRect(drawingRect, NULL);
        CTFrameRef frameRef = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, nil);
        CFArrayRef lines = CTFrameGetLines(frameRef);
        CFIndex count = CFArrayGetCount(lines);
        if (count == 0) { return height; }
        CGPoint *origins = malloc(count * sizeof(CGPoint));
        if (origins == NULL) { return height; }
        CTFrameGetLineOrigins(frameRef, CFRangeMake(0, 0), origins);
        CTLineRef lastline = CFArrayGetValueAtIndex(lines, count - 1);
        CGPoint lastorigin = origins[count - 1];
        CGFloat lineY = lastorigin.y;
        CGFloat lineAscent = 0;
        CGFloat lineDescent = 0;
        CGFloat lineLeading = 0;
        CTLineGetTypographicBounds(lastline, &lineAscent, &lineDescent, &lineLeading);
        height = maxH - lineY + ceilf(lineDescent);
    }
    return height;
}
/// 获取一行文字的 Range
/// - Parameter line: CTLine
/// - Returns: 一行文字的 Range
+ (NSRange)getLineRange:(CTLineRef)line {
    NSRange range = NSMakeRange(NSNotFound, 0);
    if (line != NULL) {
        CFRange lineRange = CTLineGetStringRange(line);
        range = NSMakeRange(lineRange.location == kCFNotFound ? NSNotFound : lineRange.location, lineRange.length);
    }
    return range;
}
/// 获取行高
/// - Parameter line: CTLine
/// - Returns: 行高
+ (CGFloat)getLineHeight:(CTFrameRef)frameRef {
    if (frameRef == NULL) { return 0; }
    CFArrayRef lines = CTFrameGetLines(frameRef);
    CFIndex count = CFArrayGetCount(lines);
    if (count == 0) { return 0; }
    CTLineRef tempLine = CFArrayGetValueAtIndex(lines, 0);
    return [self getLineHeightByLine:tempLine];
}
/// 获取行高
/// - Parameter line: CTLine
/// - Returns: 行高
+ (CGFloat)getLineHeightByLine:(CTLineRef)line {
    if (line == NULL) { return 0; }
    CGFloat lineAscent = 0;
    CGFloat lineDescent = 0;
    CGFloat lineLeading = 0;
    CTLineGetTypographicBounds(line, &lineAscent, &lineDescent, &lineLeading);
    return lineAscent + lineDescent + lineLeading;
}
@end
